/*
 * socket.cpp
 *
 *  Created on: 17.01.2012
 *      Author: knz
 */

#include "socket.h"

#include <cerrno>
#include <fcntl.h>

using namespace std;

Socket::Socket(bool nonblocked)
{
    m_bNonblocked = nonblocked;

    m_iSockaddrClientSize = sizeof(m_sockaddrClient);

    IF_DEBUG_LEVEL
    LG_DEBUG("Start socket:");

    m_iDescriptor = socket(PF_INET, SOCK_STREAM, 0);
    if (m_iDescriptor<0) {
        m_bIsValid = false;
        m_arcLastError = ARC_ERRNO;
        IF_ERR_LEVEL
        LV_ERR("[ZSocket::ZSocket()] %s", strerror(errno));
    } else {
        m_bIsValid = true;
        m_arcLastError = ARC_SUCCESS;
        if (m_bNonblocked) {
            fcntl(m_iDescriptor, F_SETFL, O_NONBLOCK);
        }
    }
}

Socket::~Socket()
{
    doClose();
}

bool Socket::doBind(const char *pchDotAddress, int iPort)
{
    m_zIpAddress.clear();
    m_zIpAddress.append(pchDotAddress);

    if (!m_bIsValid) {
        IF_ERR_LEVEL
        LG_ERR("[ZSocket::doBind()] Can't bind socket. Socket is not valid.");
        return false;
    }

    m_sockaddrServer.sin_family = AF_INET;
    m_sockaddrServer.sin_port = htons(iPort);
    int res = inet_aton(pchDotAddress, &(m_sockaddrServer.sin_addr));
    if (res == 0) {
        IF_ERR_LEVEL
        LV_ERR("[ZSocket::doBind()] %s", strerror(errno));
        return false;
    }

    res = bind(m_iDescriptor, (struct sockaddr*)&m_sockaddrServer, sizeof(m_sockaddrServer));
    if (res<0) {
        IF_ERR_LEVEL
        LV_ERR("[ZSocket::doBind()] %s", strerror(errno));
        return false;
    }

    IF_NOTICE_LEVEL
    LV_NOTICE("[ZSocket::doBind()] Bind passive socket with %s#%d", pchDotAddress, iPort);

    return true;
}

bool Socket::doConnect(const char *pchDotAddress, int iPort)
{
    m_zIpAddress.clear();
    m_zIpAddress.append(pchDotAddress);

    if (!m_bIsValid) {
        IF_ERR_LEVEL
        LG_ERR("[ZSocket::doConnect()] Can't connect socket. Socket is not valid.");
        m_arcLastError = ARC_INVALID_SOCKET;
        return false;
    }

    m_sockaddrServer.sin_family = AF_INET;
    m_sockaddrServer.sin_port = htons(iPort);
    int res = inet_aton(pchDotAddress, &(m_sockaddrServer.sin_addr));
    if (res == 0) {
        IF_DEBUG_LEVEL
        LV_DEBUG("[ZSocket::doConnect()] %s", strerror(errno));
        m_arcLastError = ARC_ERRNO;
        return false;
    }

    res = connect(m_iDescriptor, (struct sockaddr*)&m_sockaddrServer, sizeof(m_sockaddrServer));
    if (res<0) {
        if (errno == EINPROGRESS) {
            // особый случай - подключение к неблокирующему сокету!!! Требуется взаимная синхронизация.
            fd_set writeset;
            FD_ZERO(&writeset);
            FD_SET(m_iDescriptor, &writeset);
            timeval timeout;
            timeout.tv_sec = 1;
            timeout.tv_usec = 0;
            res = select(m_iDescriptor+1, 0, &writeset, 0, &timeout);
            if (res>0) {
                int optval;
                socklen_t optlen = sizeof(optval);
                res = getsockopt(m_iDescriptor, SOL_SOCKET, SO_ERROR, &optval, &optlen);
                if (res<0) {
                    IF_DEBUG_LEVEL
                    LV_DEBUG("[ZSocket::doConnect()] %s", strerror(errno));
                    m_arcLastError = ARC_ERRNO;
                    return false;
                }
                if (optval != 0) {
                    IF_DEBUG_LEVEL
                    LG_DEBUG("Can't connect to noblocked socket.");
                    m_arcLastError = ARC_NONBLOCKED_SOCKET_ERROR;
                    return false;
                }
            }
        } else {
            m_arcLastError = ARC_ERRNO;
            IF_DEBUG_LEVEL
            LV_DEBUG("[ZSocket::doConnect()] %s", strerror(errno));
            return false;
        }
    }

    IF_DEBUG_LEVEL
    LV_DEBUG("Connect active socket to %s#%d", pchDotAddress, iPort);

    m_arcLastError = ARC_SUCCESS;
    return true;
}

bool Socket::doListen()
{
    if (!m_bIsValid) {
        IF_ERR_LEVEL
        LG_ERR("[ZSocket::doListen()] Can't run socket to listen. Socket is not valid.");
        return false;
    }

    int res = listen(m_iDescriptor,10);
    if (res<0) {
        IF_ERR_LEVEL
        LV_ERR("[ZSocket::doListen()] %s", strerror(errno));
        return false;
    }

    IF_NOTICE_LEVEL
    LG_NOTICE("[ZSocket::doListen()] Listen to socket for client connecting ...");

    return true;
}

bool Socket::doAccept(int &iClientDescriptor, std::string &sIpAddress)
{
    if (!m_bIsValid) {
        IF_ERR_LEVEL
        LG_ERR("Can't run socket to accept. Socket is not valid.");
        return false;
    }

    IF_NOTICE_LEVEL
    LG_NOTICE("[ZSocket::doAccept()] Wait for accept() ...");

    iClientDescriptor = accept(m_iDescriptor,
                               (struct sockaddr*) &m_sockaddrClient,
                               (socklen_t*) &m_iSockaddrClientSize);

    if (iClientDescriptor<0) {
        IF_ERR_LEVEL
        LV_ERR("[ZSocket::doAccept()] %s", strerror(errno));
        return false;
    }

    // Извлечем IP-адресс из полученного сокета
    sIpAddress = inet_ntoa(m_sockaddrClient.sin_addr);

    IF_NOTICE_LEVEL
    LV_NOTICE("[ZSocket::doAccept()] Client connected (descriptor=%d, ip=%s)",
              iClientDescriptor, sIpAddress.c_str());

    return true;
}

void Socket::doClose()
{
    if (m_iDescriptor>=0) {
        close(m_iDescriptor);

        IF_DEBUG_LEVEL
        LV_DEBUG("[ZSocket::doClose()] Socket %d closed successfully", m_iDescriptor);

        m_iDescriptor = -1;
    }
}

// Функция сравнения возвращает:
// 0  - объекты совпадают
// -1 - ошибка записи адреса
// -2 - объект не найден
int Socket::compare(const char *dot_addr_string, int port)
{
    struct sockaddr_in sockaddr;
    int res = inet_aton(dot_addr_string, &(sockaddr.sin_addr));
    if (res == 0) {
        return -1;
    }

    if (m_sockaddrServer.sin_addr.s_addr == sockaddr.sin_addr.s_addr) {
        if (m_sockaddrServer.sin_port == htons(port)) {
            return 0;
        }
    }
    return -2;
}
